﻿using NAudio.Dsp;
using NAudio.Utils;
using NAudio.Wave;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static AudioLoudnessScan.AudioLoudnessMeter;

namespace AudioLoudnessScan
{
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Dictionary<string, AudioLoudnessEntry> existingData = ReadLoudnessConfigFile();

                // 获取当前目录下所有 .mp3 和 .wav 文件
                string[] audioFiles = Directory.GetFiles(Environment.CurrentDirectory, "*.*", SearchOption.AllDirectories)
                                               .Where(s => s.ToLower().EndsWith(".mp3") || s.ToLower().EndsWith(".wav"))
                                               .Select(Path.GetFullPath)
                                               .ToArray();

                foreach (var audioFilePath in audioFiles)
                {
                    // 将绝对路径转换为相对于当前目录的相对路径
                    string relativePath = audioFilePath.Substring(Path.GetFullPath(Environment.CurrentDirectory).Length)
                                                   .Replace('\\', '/').TrimStart('/');

                    // 检查文件是否需要重新测量
                    bool needsRemeasure = !existingData.ContainsKey(relativePath) ||
                                          (new FileInfo(audioFilePath).LastWriteTime - existingData[relativePath].FileModified).TotalSeconds > 10;

                    if (needsRemeasure)
                    {
                        FileToProcess.Enqueue(audioFilePath);
                    }
                }

                List<Thread> threads = new List<Thread>();
                for (int i = 0; i < Math.Max(1,Environment.ProcessorCount - 1); i++) { 
                    Thread t = new Thread(ProcessThread);
                    threads.Add(t);
                    t.Start();
                }
                threads.ForEach(t => t.Join());

                while (ReturnsResult.TryDequeue(out var result)) {
                    if (existingData.ContainsKey(result.RelativePath))
                    {
                        existingData[result.RelativePath] = result;
                    }
                    else
                    {
                        existingData.Add(result.RelativePath, result);
                    }
                }

                SaveLoudnessConfigFile(existingData);
                Console.WriteLine("Done!");
                Thread.Sleep(3000);
            }catch(Exception ex)
            {
                Console.WriteLine(ex.ToString());
                Console.ReadLine();
            }

        }

        static ConcurrentQueue<string> FileToProcess = new ConcurrentQueue<string>();
        static ConcurrentQueue<AudioLoudnessEntry> ReturnsResult = new ConcurrentQueue<AudioLoudnessEntry>();

        private static void ProcessThread()
        {
            while(FileToProcess.TryDequeue(out string onePath))
            {
                try
                {
                    ReturnsResult.Enqueue(DoProcessOne(onePath));
                } catch (Exception ex) { 
                    Console.WriteLine(ex.ToString()); 
                }
            }
        }

        private static AudioLoudnessEntry DoProcessOne(string audioFilePath)
        {
            // 将绝对路径转换为相对于当前目录的相对路径
            string relativePath = audioFilePath.Substring(Path.GetFullPath(Environment.CurrentDirectory).Length)
                                           .Replace('\\', '/').TrimStart('/');
            // 进行测量
            MeasureResult measurement = AudioLoudnessMeter.MeasureAudioFile(audioFilePath);

            Console.WriteLine(relativePath + "  =>  " + measurement.ToString());
            // 创建新的 AudioLoudnessEntry
            AudioLoudnessEntry newEntry = new AudioLoudnessEntry
            {
                RelativePath = relativePath,
                PeakDB = measurement.PeakDB,
                LUFS = measurement.LUFS,
                FileModified = File.GetLastWriteTime(audioFilePath)
            };
            return newEntry;
        }


        static void SaveLoudnessConfigFile(Dictionary<string, AudioLoudnessEntry> data)
        {
            string filePath = Path.Combine(Environment.CurrentDirectory, configFileName);

            using (StreamWriter writer = new StreamWriter(filePath))
            {
                foreach (var entry in data.Values)
                {
                    string line = $"{entry.RelativePath}|{entry.PeakDB}|{entry.LUFS}|{entry.FileModified.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)}";
                    writer.WriteLine(line);
                }
            }
        }
        const string configFileName = ".loudness.cfg";

        public static Dictionary<string, AudioLoudnessEntry> ReadLoudnessConfigFile()
        {
            var entries = new Dictionary<string, AudioLoudnessEntry>();
            var configFilePath = Path.Combine(Environment.CurrentDirectory, configFileName);
               

            if (!File.Exists(configFilePath))
            {
                return new Dictionary<string, AudioLoudnessEntry>();
            }

            foreach (var line in File.ReadLines(configFilePath))
            {
                if (string.IsNullOrWhiteSpace(line))
                {
                    continue; // Skip empty lines
                }

                var parts = line.Split('|');
                if (parts.Length != 4)
                {
                    throw new FormatException("Invalid line format in loudness configuration file.");
                }

                var entry = new AudioLoudnessEntry
                {
                    RelativePath = parts[0],
                    PeakDB = float.Parse(parts[1], CultureInfo.InvariantCulture),
                    LUFS = float.Parse(parts[2], CultureInfo.InvariantCulture),
                    FileModified = DateTime.ParseExact(parts[3], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)
                };
                entries.Add(entry.RelativePath, entry);
               
            }

            return entries;
        }

        public class AudioLoudnessEntry
        {
            public string RelativePath;
            public float PeakDB;
            public float LUFS;
            public DateTime FileModified;
        }
    }


    public class AudioLoudnessMeter
    {

        public static MeasureResult MeasureAudioFile(string path)
        {
            using (AudioFileReader reader = new AudioFileReader(path))
            {
                return new AudioLoudnessMeter(reader).DoMeasure();
            }
        }
        private PrefilterSampleProvider _sampleProvider;

        public AudioLoudnessMeter(ISampleProvider sampleProvider)
        {
            if (sampleProvider.WaveFormat.Channels == 1)
            {
                _sampleProvider = new PrefilterSampleProvider(sampleProvider.ToStereo());
            }
            else
            {
                _sampleProvider = new PrefilterSampleProvider(sampleProvider);
            }

        }

        private MeasureResult _result = null;

        private const float TimeWindowMS = 400; // 时间窗口
        private const float WindowOverlap = 0.75f; // 时间窗口重叠 
        private const float WindowStepping = 0.25f; // 1 - WindowOverlap // 时间窗口步长，即每次移动时间窗口时向前移动0.25倍的窗口长度
        public MeasureResult DoMeasure()
        {
            if (_result != null) { 
                return _result;
            }
            var sampleProvider = this._sampleProvider;// 此处是已滤波后的音频，立体声
            int sampleRate = sampleProvider.WaveFormat.SampleRate; // 获取音频采样率
            // 计算时间窗口内的采样数量
            int windowLengthSamples = (int)(sampleRate * (TimeWindowMS / 1000.0f)) * 2/*(立体声，声道数有2个)*/;
            // 计算时间窗口步长
            int windowSteppingSamples = (int)((float)windowLengthSamples * (1f - WindowOverlap));

            // 确保时间窗口步长是2的倍数
            windowSteppingSamples = windowSteppingSamples / 2 * 2;
            windowLengthSamples = windowSteppingSamples * 4;

            List<float> measuredWindows = new List<float>();// 已测量的每个时间窗口的
            
            
            float[] buf = new float[windowLengthSamples];
            int remainFirstWindow = buf.Length;
            int bufPtr = 0;
            bool eof = false;
            while (true) { 
                int len = sampleProvider.Read(buf, bufPtr, remainFirstWindow);
                if(len == 0)
                {
                    eof = true;
                    break;
                }
                remainFirstWindow -= len;
                if (remainFirstWindow == 0)
                {
                    break;
                }
                bufPtr += len;
                while (bufPtr >= buf.Length) { 
                    bufPtr -= buf.Length;
                }
            }
            measuredWindows.Add(CalcOneWindowSquareMean(buf));
            if (!eof)
            {
                while (true)
                {
                    int remainSubwindowLen = windowSteppingSamples;
                    int newLen = sampleProvider.Read(buf, bufPtr, remainSubwindowLen);
                    bufPtr += newLen;
                    remainSubwindowLen -= newLen;
                    if (newLen == 0)
                    {
                        measuredWindows.Add(CalcOneWindowSquareMean(buf));
                        break;
                    }
                    if (remainSubwindowLen == 0)
                    {
                        measuredWindows.Add(CalcOneWindowSquareMean(buf));
                    }

                    while (bufPtr >= buf.Length)
                    {
                        bufPtr -= buf.Length;
                    }
                }
            }
            // 此处已经计算了每一小块的值
            var threhold = -70;
            var gatedWindows = measuredWindows.Where(it => (-0.691d + 10 * Math.Log10(it)) >= threhold).ToArray();
            if(gatedWindows.Length == 0)
            {
                gatedWindows = new float[1];
                gatedWindows[0] = 0.000001f;
            }
            var windowAverate = gatedWindows.Average();
            var LUFS = (float)(-0.691d + 10 * Math.Log10(windowAverate));
            _result = new MeasureResult(LUFS, (float)Decibels.LinearToDecibels(sampleProvider.peakValue));
            return _result;
        }

        public class MeasureResult
        {
            public float LUFS;
            public float PeakDB;

            public MeasureResult(float lUFS, float peakDB)
            {
                LUFS = lUFS;
                PeakDB = peakDB;
            }

            public override string ToString()
            {
                return $"Peak = {PeakDB} dBfs; LUFS = {LUFS}";
            }
        }

        private float CalcOneWindowSquareMean(float[] buf) // buf中包含双声道数据
        {
            float len = buf.Length / 2;
            var leftSquireSum = 0f;
            var rightSquireSum = 0f;
            for (int i = 0; i < len; i++) { 
                leftSquireSum += buf[i*2] * buf[i * 2];
                rightSquireSum += buf[i*2 + 1] * buf[i * 2 + 1];
            }
            var leftSquireMean = leftSquireSum / len;
            var rightSquireMean = rightSquireSum / len;

            var channelSum = leftSquireMean + rightSquireMean;

            return channelSum;
        }

        private class PrefilterSampleProvider : ISampleProvider
        {
            private readonly ISampleProvider source;
            private readonly Prefilter prefilterLeft;
            private readonly Prefilter prefilterRight;

            public float peakValue = 0;

            public PrefilterSampleProvider(ISampleProvider source)
            {
                this.source = source;
                this.prefilterLeft = new Prefilter(WaveFormat.SampleRate);
                this.prefilterRight = new Prefilter(WaveFormat.SampleRate);
            }

            public int Read(float[] buffer, int offset, int count)
            {
                int samplesRead = source.Read(buffer, offset, count);
                int sampleCount = samplesRead / 2; // Since it's stereo, each buffer contains left and right samples

                for (int i = 0; i < sampleCount; i++)
                {
                    peakValue = Math.Max(peakValue,Math.Abs(buffer[offset + 2*i]));
                    peakValue = Math.Max(peakValue,Math.Abs(buffer[offset + 2*i + 1]));
                    // Process left channel
                    buffer[offset + 2 * i] = prefilterLeft.Process(buffer[offset + 2 * i]);
                    //buffer[offset + 2 * i] *= 1.07f;
                    // Process right channel
                    buffer[offset + 2 * i + 1] = prefilterRight.Process(buffer[offset + 2 * i + 1]);
                    //buffer[offset + 2 * i + 1] *= 1.07f;
                }

                return samplesRead;
            }

            public WaveFormat WaveFormat { get=>source.WaveFormat; }
        }

        private class Prefilter
        {
            BiQuadFilter filter1 = null;
            BiQuadFilter filter2 = null;
            float postGan = 0;
            public Prefilter(int sampleRate)
            {
                // R-REC-BS.1770-0-200607-S 规范参数
                filter1 = BiQuadFilter.LowShelf(sampleRate, 1500f, 12.8359f * 0.0748f, -4f);
                filter2 = BiQuadFilter.HighPassFilter(sampleRate, 38, 0.5f);
                postGan = (float)Decibels.DecibelsToLinear(4);

                //// 个人听音喜好
                //filter1 = BiQuadFilter.LowShelf(sampleRate, 5400f, 12.8359f * 0.2f, -7f);
                //filter2 = BiQuadFilter.HighPassFilter(sampleRate, 100, 0.9f);
                //postGan = (float)Decibels.DecibelsToLinear(7);
            }

            public float Process(float value)
            {
                return filter2.Transform(filter1.Transform(value)) * postGan;
            }
        }
    }
}
